package cn.xpride.framework.http;

import cn.xpride.framework.http.annotation.GetMapping;
import cn.xpride.framework.http.annotation.PostMapping;
import cn.xpride.framework.http.annotation.RestBody;
import cn.xpride.framework.http.annotation.RestQuery;
import cn.xpride.framework.utils.HttpUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.smartboot.http.common.enums.HeaderNameEnum;
import org.smartboot.http.common.enums.HttpMethodEnum;
import org.smartboot.http.common.enums.HttpStatus;
import org.smartboot.http.common.utils.AntPathMatcher;
import org.smartboot.http.common.utils.DateUtils;
import org.smartboot.http.common.utils.Mimetypes;
import org.smartboot.http.common.utils.StringUtils;
import org.smartboot.http.server.HttpRequest;
import org.smartboot.http.server.HttpResponse;
import org.smartboot.http.server.HttpServerHandler;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.*;

@Slf4j
public class SmartHttpContextHandler extends HttpServerHandler {

    private static final int READ_BUFFER = 1024 * 1024;

    private static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();

    SmartHttpContextHandler() {
        URL distURL = SmartHttpContextHandler.class.getClassLoader().getResource("./dist");
        if (null != distURL) {
            String path = distURL.getPath();
            if(new File(path).exists()) {
                log.info("静态目录: path = {} ", path);
                SmartHttpContext.staticDir = path;
            }
        }
        URL propFileURL = SmartHttpContextHandler.class.getClassLoader().getResource("application.properties");
        if(null != propFileURL){
            File propFile = new File(propFileURL.getPath());
            if(propFile.exists()){
                SmartHttpContext.classDir = propFile.getParentFile().getAbsolutePath();
            }
        }
    }

    @Override
    public void handle(HttpRequest request, HttpResponse response) throws IOException {
        String uri = request.getRequestURI();
        String method = request.getMethod();
        log.debug("请求Method: {} , URL: {}", method, uri);
        boolean isOk = tryHandleRest(uri, method, request, response);
        if (!isOk) {
            handleDist(uri, method, request, response);
        }
    }

    private boolean tryHandleRest(String uri, String method, HttpRequest request, HttpResponse response) {
        Method targetMethod = matchHandlerMethod(uri, method);
        if (null != targetMethod) {
            Object obj = SmartHttpContext.methodObjectMap.get(targetMethod);
            if (null != obj) {
                Object[] args;
                try {
                    args = getMethodArgumentAndValues(targetMethod, request);
                } catch (Exception e) {
                    log.debug("参数解析异常",e);
                    response.setHttpStatus(HttpStatus.BAD_REQUEST);
                    return true;
                }
                try {
                    SmartHttpContext.currentSet(request);
                    Object ret = targetMethod.invoke(obj, args);
                    response.setHttpStatus(HttpStatus.OK);
                    response.setHeader(HeaderNameEnum.CONTENT_TYPE.getName(), "application/json; charset=utf-8");
                    response.setHeader("X-Content-Type-Options", "nosniff");
                    byte[] data = new byte[0];
                    if(null != ret){
                        data = JSONObject.toJSONString(ret).getBytes();
                    }
                    response.setContentLength(data.length);
                    response.write(data);
                    return true;
                } catch (Exception e) {
                    log.error("反射处理http业务方法异常", e.getCause());
                    response.setHttpStatus(HttpStatus.INTERNAL_SERVER_ERROR);
                    return true;
                } finally {
                    SmartHttpContext.currentRemove();
                }
            }
        }
        return false;
    }

    private Object[] getMethodArgumentAndValues(Method targetMethod, HttpRequest request) throws Exception{
        String method = request.getMethod();
        int parameterCount = targetMethod.getParameterCount();
        Object[] args = new Object[parameterCount];
        Parameter[] parameters = targetMethod.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            Parameter p = parameters[i];
            //不论get还是post都有可能有query参数
            RestQuery queryParam = p.getAnnotation(RestQuery.class);
            if (null != queryParam) {
                String s = request.getParameter(queryParam.value());
                String name = p.getType().getName();
                if ("java.lang.String".equals(name)) {
                    args[i] = s;
                } else if ("java.lang.Long".equals(name) || "long".equals(name)) {
                    args[i] = null != s ? Long.valueOf(s) : ("long".equals(name) ? 0L : null);
                } else if ("java.lang.Integer".equals(name) || "int".equals(name)) {
                    args[i] = null != s ? Integer.valueOf(s) : ("int".equals(name) ? 0 : null);
                } else if ("java.lang.Short".equals(name) || "short".equals(name)) {
                    args[i] = null != s ? Short.valueOf(s) : ("short".equals(name) ? (short)0 : null);
                } else if ("java.lang.Double".equals(name) || "double".equals(name)) {
                    args[i] = null != s ? Double.valueOf(s) : ("double".equals(name) ? 0D : null);
                } else if ("java.lang.Float".equals(name) || "float".equals(name)) {
                    args[i] = null != s ? Float.valueOf(s) : ("float".equals(name) ? 0F : null);
                }
            }
            if (HttpMethodEnum.POST.getMethod().equals(method)){
                RestBody postBody = p.getAnnotation(RestBody.class);
                if(null != postBody){
                    byte[] buffer = HttpUtil.toByteArray(request.getInputStream());
                    boolean isString = String.class.isAssignableFrom(p.getType());
                    if(isString){
                        args[i] = new String(buffer);
                    }
                    boolean isMap = Map.class.isAssignableFrom(p.getType());
                    if(isMap){
                        args[i] = JSONObject.parseObject(new String(buffer));
                    }
                    boolean isCol = Collection.class.isAssignableFrom(p.getType());
                    if(isCol){
                        //默认处理成List
                        Type[] types = ((ParameterizedType) p.getParameterizedType()).getActualTypeArguments();
                        Type t = types[0];
                        boolean isSet = Set.class.isAssignableFrom(p.getType());

                        if (t.getTypeName().startsWith(Map.class.getTypeName())) {
                            //针对 List<Map<String,Object>> param 这种参数类型
                            if (isSet){
                                args[i] = new HashSet<>(JSONArray.parseArray(new String(buffer)));
                            }else {
                                args[i] = JSONArray.parseArray(new String(buffer));
                            }
                        }else {
                            if (isSet){
                                args[i] = new HashSet<>(JSONArray.parseArray(new String(buffer),Class.forName(t.getTypeName())));
                            }else {
                                args[i] = JSONArray.parseArray(new String(buffer),Class.forName(t.getTypeName()));
                            }
                        }
                    }

                    if(null == args[i]){ //前面都不配,可能是常规java的pojo类
                        args[i] = JSONObject.parseObject(new String(buffer),p.getType());
                    }
                }
            }
        }
        return args;
    }

    private Method matchHandlerMethod(String originUri, String method) {
        if (null == originUri) return null;
        String uri = originUri;
        while (uri.endsWith("/")) {
            uri = uri.substring(0, uri.length() - 1);
        }
        Method httpHandler = SmartHttpContext.uriMethodMap.get(uri);
        if (httpHandler == null) {
            for (Map.Entry<String, Method> entity : SmartHttpContext.uriMethodMap.entrySet()) {
                if (PATH_MATCHER.match(entity.getKey(), uri)) {
                    httpHandler = entity.getValue();
                    break;
                }
            }
        }
        if (null != httpHandler) {
            if (
                    (HttpMethodEnum.GET.getMethod().equals(method) && null == httpHandler.getAnnotation(GetMapping.class)) ||
                            (HttpMethodEnum.POST.getMethod().equals(method) && null == httpHandler.getAnnotation(PostMapping.class))
            ) {
                httpHandler = null;
            }
        }
        return httpHandler;
    }


    private void handleDist(String uri, String method, HttpRequest request, HttpResponse response) throws IOException {
        String fileName = uri;
        if ("/".equals(uri)) {
            fileName += "index.html";
        }
        File file = new File(SmartHttpContext.staticDir, URLDecoder.decode(fileName, "utf8"));
        //404
        if (!file.isFile()) {
            File indexHtmlFile = new File(SmartHttpContext.staticDir, "/index.html");
            if (!indexHtmlFile.isFile()) {
                log.debug("try file: {} not found!", fileName);
                response.setHttpStatus(HttpStatus.NOT_FOUND);
                response.setHeader(HeaderNameEnum.CONTENT_TYPE.getName(), "text/plain; charset=utf-8");
                response.setHeader("X-Content-Type-Options", "nosniff");
                byte[] data = "404 page not found".getBytes();
                response.setContentLength(data.length);
                response.write(data);
                return;
            } else {
                log.info("request url {} not found,try file index.html", uri);
                file = indexHtmlFile;
            }
        }
        //304
        Date lastModifyDate = new Date(file.lastModified());
        try {
            String requestModified = request.getHeader(HeaderNameEnum.IF_MODIFIED_SINCE.getName());
            if (StringUtils.isNotBlank(requestModified) && lastModifyDate.getTime() <= DateUtils.parseLastModified(requestModified).getTime()) {
                response.setHttpStatus(HttpStatus.NOT_MODIFIED);
                return;
            }
        } catch (Exception e) {
            log.error("exception", e);
        }
        response.setHeader(HeaderNameEnum.LAST_MODIFIED.getName(), DateUtils.formatLastModified(lastModifyDate));


        String contentType = Mimetypes.getInstance().getMimetype(file);
        response.setHeader(HeaderNameEnum.CONTENT_TYPE.getName(), contentType + "; charset=utf-8");
        //HEAD不输出内容
        if (HttpMethodEnum.HEAD.getMethod().equals(method)) {
            return;
        }

        if (!file.getName().endsWith("html") && !file.getName().endsWith("htm")) {
            response.setContentLength((int) file.length());
        }

        FileInputStream fis = new FileInputStream(file);
        FileChannel fileChannel = fis.getChannel();
        long fileSize = fileChannel.size();
        long readPos = 0;
        while (readPos < fileSize) {
            MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, readPos, fileSize - readPos > READ_BUFFER ? READ_BUFFER : fileSize - readPos);
            readPos += mappedByteBuffer.remaining();
            byte[] data = new byte[mappedByteBuffer.remaining()];
            mappedByteBuffer.get(data);
            response.write(data);
        }
        fileChannel.close();
        fis.close();

    }

}
